现在你理解了,一个对象可以有synchronized方法或其他形式的加锁机制来防止别的任务在互斥还没有释放的时候就访问这个对象。你已经学习过,任务可以变成阻塞状态,所以就可能出现两种情况:某个惹我怒在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间相互等待的连续循环,没有哪个线程恩给你继续。这被称之为 死锁 。
如果你运行一个程序,而它马上就死锁了,你可以立即跟踪下去。真正的问题在于,程序可能看起来工作良好,但是具有潜在的死锁危险。这时,死锁可能发生,而事先却没有任何征兆,所以缺陷会潜伏在你的程序里,直到客户发现它出乎意料地发生(以一种几乎肯定是很难重现的方式发生)。因此,在编写并发程序的时候,进行仔细的程序设计以防止死锁是关键部分。
由Edsger Dijkstra提出的 哲学家就餐 问题是一个经典的死锁例证。该问题的基本描述中是指定五个哲学家(不过这里的例子中将允许任意数目)。这些哲学家将花费时间思考,花部分时间就餐。当他们思考的时候,就不需要任何共享资源;但当他们就餐时,将使用有限数量的餐具。在问题的原始描述中,餐具是叉子。要吃到桌子中央盘子里的意大利面条需要用两把叉子,不过把餐具看成是筷子更合理;很明显,哲学家要就餐就需要两根筷子。
问题中引入的难点是:作为哲学家,他们很穷,所以他们只能买五根筷子(更一般地讲,筷子和哲学家的数量相同)。他们围坐在一个桌子周围,每人之间放一根筷子。当一个哲学家要就餐的时候,这个哲学家必须同时得到左边和右边的筷子。如果一个哲学家左边或右边已经有人在使用筷子了,那么这个哲学家就必须等待,直到可得到必须的筷子。
/**
* 筷子
*
* 任何两个哲学家(Philosopher)都不能成功take()同一根筷子。另外,如果一根筷子(Chopstick)已经被某个哲学家(Philosopher)获得,
* 那么另一个Philosopher可以wait(),直到这根Chopstick的当前持有者调用drop()使其可用为止。
*
* 当一个Philosopher任务调用take()时,这个philosopher将等待,直至taken标志为false(直至当前持有Chopstick的Philosopher释放它)。
* 然后这个任务会将taken标志设置为true,以表示现在由新的Philosopher持有这根Chopstick。当这个Philosopher使用完这根Chopstick时,
* 它会调用drop()来修改标志的状态,并notif